﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using MiscUtil.IO;

namespace RemoteImaging.LicensePlate
{
    class LprSender
    {
        private static LprSender _instance;
        private readonly string _server;
        private readonly int _port;
        private AutoResetEvent _connectFinish = new AutoResetEvent(false);
        ConcurrentQueue<LicensePlateSpec> _licensePlateInfos =
            new ConcurrentQueue<LicensePlateSpec>();

        private ConcurrentDictionary<string, TcpClient> _clients =
            new ConcurrentDictionary<string, TcpClient>();

        protected static Encoding GbkEncoding;
        protected static Encoding AsciiEncoding;
        private TcpClient _tcpClient;

        static LprSender()
        {
            GbkEncoding = Encoding.GetEncoding("GB2312");
            AsciiEncoding = Encoding.ASCII;
        }


        LprSender(string server, int port)
        {
            if (server == null) throw new ArgumentNullException("server");
            _server = server;
            _port = port;
        }

        public static LprSender Instance(string server, int port)
        {
            if (_instance == null)
            {
                _instance = new LprSender(server, port);
                _instance.Start();
            }

            return _instance;
        }


        public void Start()
        {
            System.Threading.Tasks.Task.Factory.StartNew(StartConnect);
            System.Threading.Tasks.Task.Factory.StartNew(SendLprThread);

        }

        public void Enqueue(LicensePlateSpec lpi)
        {
            if (_licensePlateInfos.Count >= 10)
            {
                LicensePlateSpec dummy;
                _licensePlateInfos.TryDequeue(out dummy);
            }

            _licensePlateInfos.Enqueue(lpi);
            System.Diagnostics.Debug.WriteLine("add lpi in queue");

        }

        private void StartConnect()
        {
            try
            {
                System.Threading.Thread.Sleep(1000);
                _tcpClient = new TcpClient();
                _tcpClient.ReceiveTimeout = 5000;
                _tcpClient.SendTimeout = 5000;

                _tcpClient.BeginConnect(
                    Properties.Settings.Default.UploadServer,
                    Properties.Settings.Default.UploadServerPort,
                    ConnectCallback,
                    _tcpClient);

            }
            catch (Exception ex)
            {
                _tcpClient.Close();
                StartConnect();
            }
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            var client = (TcpClient)ar.AsyncState;

            try
            {
                client.EndConnect(ar);
                var ep = (IPEndPoint)client.Client.RemoteEndPoint;
                _clients.TryAdd(ep.Address + ":" + ep.Port, client);
                System.Diagnostics.Debug.WriteLine("connected");
                _connectFinish.WaitOne();
                StartConnect();

            }
            catch (Exception)
            {
                client.Close();
                StartConnect();
            }


        }

        private void SendLprThread()
        {
            while (true)
            {
                if (_clients.Count == 0 || _licensePlateInfos.Count == 0)
                {
                    System.Threading.Thread.Sleep(3000);
                    continue;
                }

                LicensePlateSpec lpi = null;
                if (_licensePlateInfos.TryDequeue(out lpi))
                {
                    try
                    {
                        byte[] buffer = PackLpr(lpi);
                        var clients = _clients.ToList();
                        foreach (var tcpClient in clients)
                        {
                            try
                            {
                                tcpClient.Value.GetStream().Write(buffer, 0, buffer.Length);
                                tcpClient.Value.GetStream().Flush();
                            }
                            catch (Exception)
                            {
                                TcpClient c;
                                if (_clients.TryRemove(tcpClient.Key, out c))
                                {
                                    c.Close();
                                    _connectFinish.Set();
                                }

                            }
                        }
                    }
                    catch (Exception)
                    {
                    }

                }
            }

        }

        private static byte[] PackLpr(LicensePlateSpec lpi)
        {
            //body first to calculate value length
            byte[] payload = PackPayload(lpi);

            using (var valueStream = new MemoryStream())
            using (var writer = new EndianBinaryWriter(MiscUtil.Conversion.EndianBitConverter.Big, valueStream))
            {
                writer.Write((short)0);
                writer.Write((short)1000);
                writer.Write(payload.Length);
                writer.Write(payload);
                return valueStream.ToArray();
            }
        }

        private static byte[] PackPayload(LicensePlateSpec lpi)
        {
            using (var valueStream = new MemoryStream())
            using (var valueWriter = new EndianBinaryWriter(MiscUtil.Conversion.EndianBitConverter.Big, valueStream))
            {
                var mb = new byte[32];//监控点编号
                AsciiEncoding.GetBytes(Properties.Settings.Default.MonitorId, 0,
                                       Properties.Settings.Default.MonitorId.Length, mb, 0);
                valueWriter.Write(mb);

                var db = new byte[8];
                var dir = AsciiEncoding.GetBytes("01", 0, 2, db, 0);
                valueWriter.Write(db);
                valueWriter.Write(1);//相机编号
                valueWriter.Write(1);//道路编号

                var lprBuffer = new byte[16];
                GbkEncoding.GetBytes(lpi.LicensePlateNumber, 0, lpi.LicensePlateNumber.Length, lprBuffer, 0);
                valueWriter.Write(lprBuffer);


                valueWriter.Write(lpi.PlateX);//车牌位置
                valueWriter.Write(lpi.PlateY);
                valueWriter.Write(lpi.PlateW);
                valueWriter.Write(lpi.PlateH);

                valueWriter.Write(90); ;//车牌识别置信度
                var colorCode = Converter.ConvertWtColorToKzColor(lpi.LicensePlateColor);
                valueWriter.Write((short)colorCode);//车牌颜色
                valueWriter.Write((short)0);//车类
                valueWriter.Write((short)4);//车身颜色, unknown=4
                valueWriter.Write((byte)0);//检测方式

                valueWriter.Write(lpi.isDrivingReversly ? (byte)1 : (byte)0);//是否违章
                WriteTime(lpi.CaptureTime, valueWriter);//红灯开始时间

                var violationTypeString = lpi.isDrivingReversly ? "1301" : "";
                var violationBytes = new byte[32];//违章类型
                var violationType = AsciiEncoding.GetBytes(violationTypeString, 0, violationTypeString.Length, violationBytes, 0);
                valueWriter.Write(violationBytes);

                valueWriter.Write(0);//车速
                valueWriter.Write(0); //速度限制

                var packIdBuffer = new byte[64];//包id
                var packIdString = Guid.NewGuid().ToString();
                AsciiEncoding.GetBytes(packIdString, 0, packIdString.Length, packIdBuffer, 0);
                valueWriter.Write(packIdBuffer);

                var imgCount = 1;
                valueWriter.Write((short)1);//图片数目

                for (int i = 0; i < imgCount; i++)
                {
                    WriteTime(lpi.CaptureTime, valueWriter);//图片抓拍时间
                    var imgBuffer = GetImageBuffer(lpi.Image);
                    valueWriter.Write(imgBuffer.Length);//图像长度

                    valueWriter.Write(imgBuffer);//图像数据
                }

                valueWriter.Write((byte)0);//视频数量


                return valueStream.ToArray();
            }
        }

        private static byte[] GetImageBuffer(Image image)
        {
            byte[] imageBuffer = null;
            using (var s = new MemoryStream())
            {
                image.Save(s, System.Drawing.Imaging.ImageFormat.Jpeg);
                imageBuffer = s.ToArray();
            }
            return imageBuffer;
        }

        private static void WriteTime(DateTime time, EndianBinaryWriter valueWriter)
        {
            valueWriter.Write((short)time.Year);
            valueWriter.Write((short)time.Month);
            valueWriter.Write((short)time.Day);
            valueWriter.Write((short)time.Hour);
            valueWriter.Write((short)time.Minute);
            valueWriter.Write((short)time.Second);
            valueWriter.Write((short)time.Millisecond);
        }
    }
}
